Design patterns for container-based distributed systems
Introduction
コンテナ化されたマイクロサービスアーキテクチャが一般的になってきていることが背景。
この記事ではコンテナベースの分散システムのデザインパターンについて述べる。
single-container patterns for container management
Pod単位での管理方法
single-node patterns of closely cooperating containers
1 Node内での複数Podの協調のさせかた
multi-node patterns for distributed algorithms.
複数Nodeでの分散のさせかた
Distributed system design patterns
今日の分散システムデザインの最先端は1980年代のプログラミング界隈におけるOOPの勃興を思わせる。
例えばMapReduceのパターンはビッグデータプログラミングを容易にした。しかしMapRedueceは主にApache Hadoopのプログラミング言語であったJavaに限定された。しかしもっと一般化されるべきだった。
最近はLinuxコンテナ技術が採用されてきつつある。
コンテナはより良いデプロイの道具であるだけでなくOOPにおけるオブジェクトのようなものとして捉えられると思っている。それが分散システムのデザインパターンを可能にしている。
以降では分散システムのパターンについて記述していく。
Single-container management patterns
コンテナはアプリケーション専用のインタフェースだけではなく管理用のインタフェースも持つ。
かつては run , pause , stop のような動詞しか使えなかった。
しかしよりリッチなコンテナインタフェースはJSONなどで管理APIを定義する。
上流向けには下記のようなものを公開するだろう。
application-specific monitoring metrics (QPS, applica-tion health, etc.)
サービス故障検知に使う
profiling information of interest to developers (threads, stack, lock contention, network message statistics, etc.)
性能問題の検知や改善に使う
component configuration information
動的なクラスタの構成に使う
component logs
metricデータ以外のイベント収集に使う, 障害解析に役立つ
下流向けには管理システムから制御されるライフサイクルを定義する。
例えばタスクに優先度がある場合、高優先のタスクのために低優先のタスクが停止されることがある。すなわち低優先のタスクが任意のタイミングで停止されることを開発者は意識する必要がある。
もし管理システムとアプリケーションの間に厳格なライフサイクル定義があるのなら、それに従うことで開発は容易になる。
(これはつまりコンテナのCNIのことを言っているのかな?)
Dockerではgraceful shutdownをSIGTERMで解釈する。
例えばAndroid Activity Modelではコールバックの呼ばれ方によって状態機械を定義している。(どういう意味?)
コンテナアプリケーションではコンテナのライフサイクルに対するフックを下流向けインタフェースとする。
所感
upwardすなわち上流向けのコンテナインタフェースというのはいわばKubernetesから見た操作の集合。
一方でdownwardすなわち下流向けのインタフェースというのはコンテナ化されるアプリケーションが満たすライフサイクルメソッドのようなものだと思われる。
これとか?
Single-node, multi-container application patterns
ここでは複数のコンテナを同時にスケジューリングする単位すなわちKubernetesでいうPodのパターンについて述べる。
Sidecar pattern
mainコンテナとおまけのコンテナをセットにしてPod化する方法。
例
webサーバとログ収集プロセス
静的コンテンツをファイルシステムからserveするwebサーバ&gitリポジトリの変更を定期的に同期してコンテンツを更新するプロセス (この場合はvolumeは共有しているものと考えられる)
何故ならば。。。
これらを一つのコンテナに押し込むこともできるが、コンテナはあくまでもリソース確保のポリシーを表す単位として使う。
cgroupとしてリソースの配分を考えた場合、webサーバは低遅延のレスポンスを要求されるがログ収集は空いたCPU時間でやればいい。
また、コンテナはパッケージ化の単位でもある。役割、責任の分担からパッケージとしても分かれる。
コンテナは再利用の単位でもある。sidecar自体は他のmainコンテナとやはりセットで使える
コンテナによる分離で部分が故障しても全体に波及しないようにできる
コンテナはデプロイの単位でもある。アップグレードやロールバックといった世代管理の単位でもある。
逆にいうとシステムのテストマトリクスはコンテナのバージョンの積になるので膨大となるデメリットもある
モノリシックアプリケーションではこういうことはない
分割されたコンポーネントに対するテストは基本的に容易である。
これらのメリットは以降で説明するパターンでも同様に有効である。
(思ったが、個々のサブシステムに対するテストが万全であれば、合成されたシステムは健常であるという性質が明らかであればこの議論は有効)(現実にはそのような加法性はないと思われる 加法性: 任意の x, y に対して f(x + y) = f(x) + f(y) すなわち test_passed(x + y) = test_passed(x) + test_passed(y) とはならないということ。
Ambassador pattern
mainコンテナで発生する通信を代理する。
アプリケーションは一つのmemcachedと対話しているつもりだが、実はambassadorが背後にいるシャーディングクラスタにバランシングしている。
(たぶんEnvoyがこのパターンだ)
アプリケーションはlocalhostにいる一つのサーバと対話すればよい
テストではambassadorの代わりに一つのmemcachedを立ち上げればいい
ambassadorの実装は別のものに置き換えることもできる
ambassadorは同一pod内で動作するためlocalhostとしてアクセスができる。(というのがミソらしい)
Adapter pattern
外部からみたアプリケーションの単純なviewを提供する。複数のコンテナに統一されたインタフェースを提供させるために存在する。
たとえば監視用のインタフェースとしてadapterを使うことができる。
色んなやり方で作られたコンテナそれぞれを一つのmonitoringインフラからデータ収集できるようにできる。
(たぶんPrometheusで言えばアプリを監視するアプリ固有のexporterなどをadapterとして仕込んでprometheusからpollingさせるなどのやりかたがあると思われる)
ZabbixなどはsnmpやZabbix agentなど複数のモニタリング実装を使って監視できるようになっているが、関心の分離からするとちょっと散らかっていると言わざるを得ない。
(のでprometheusは非常に統一された監視アーキテクチャを持つ、とGoogleは言いたいのだろう)
(こうして見てみるとambassadorやadapterはsidecarの特殊化されたバージョンだと思っていいみたい)
Multi-node application patterns
(この辺から分散アーキテクチャの本領発揮というか面白いところが出てきそうなので期待)
Leader election pattern
分散システムでの共通パターンとしてリーダー選定が挙げられる。
レプリケーションなどではコンポーネントを複製することができるが、アプリケーションからはどれがleaderであるかを識別できる必要がある。
複数のリーダー選定を並行して行うことも考えられる。
こうしたリーダー選定は理解するのも正しく使うのも難しい。
そこでリーダー選定用のコンテナをメインから分離して、それらだけでリーダー選定の調停を行わせる。アプリケーションはlocalhostにいるリーダー選定sidecarにWeb APIなどで問い合わせるだけでいい。
(ただ、選定はヘルスチェックも含むだろうからやっぱりconsulとかになるのでは)
Work queue pattern
work queueもよくある分散システムのパターンである。かつては特定の言語で書かれたフレームワークなどによってこれが実現されていた。
しかしコンテナアプリケーションでは、キューからの取り出しと書き込みを、コンテナによるファイルの読み込みと書き込みに変換できる。それを糊付けしてフレームワークに渡す部分はコンテナの実行環境が行う。
アプリケーションはキューと対話するプロトコルや特有の言語を備える必要がない。
Scatter gather pattern
計算を木構造に分解して各リーフに並列計算させて、結果をまとめてroottが合成する。
(関数型プログラミングにおいては、rootに大きな状態ツリーが渡されて、サブルーチンは部分木にfocusとmodifyしていく)